/**
 * Part of the New Version of Pong
 * Created by Wilson Burhan and Tyler Simrell
 * Using a code base by Matthew Alden
 * Date Finished: 6/12/13
 * Version: 1.5
 */

//PongMain.java
//Code base by Matthew Alden
//Modified by Tyler Simrell and Wilson Burhan

import com.sun.j3d.utils.universe.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Random;

import javax.media.j3d.*;
import javax.swing.*;


public class PongMain 
{
	// Physics updates per second (approximate).
	private static final int UPDATE_RATE = 30;
	// Number of full iterations of the collision detection and resolution system.
	private static final int COLLISION_ITERATIONS = 4;
	// Width of the extent in meters.
	public static final float EXTENT_WIDTH = 20;

	private final HalfSpace[] boundaries;
	private final ArrayList<PhysicsObject> objects;
	private final PhysicsObject[] paddles;
	private BranchGroup scene;
	private int numPlayer = 1;
	private JLabel p1ScoreLabel;
	private JLabel p2ScoreLabel;
	private int p1Score = 0;
	private int p2Score = 0;
	private JFrame appFrame;
	private Timer timer;
	private boolean gameOver = false;
	private Triangle multishape;
	
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				new PongMain().createAndShowGUI();
			}
		});
	}

	public PongMain() {
		Object[] choice = {"Single Player Mode", "Two-Player Mode"};
		int n = JOptionPane.showOptionDialog(null, "Choose the game mode. ", "Mode", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE, null, choice, choice[0]);
		
		if (n == JOptionPane.CLOSED_OPTION){
			System.exit(0);
		}
		
		if (n == JOptionPane.NO_OPTION) 
			numPlayer = 2;
		
		paddles = new PhysicsObject[numPlayer];
		if(numPlayer == 1)
		{
			boundaries = new HalfSpace[] {new HalfSpace(-EXTENT_WIDTH, -EXTENT_WIDTH/2, 0, 1),
										  new HalfSpace(EXTENT_WIDTH, EXTENT_WIDTH/2, 0, -1),
		                                  new HalfSpace(EXTENT_WIDTH, EXTENT_WIDTH/2, -1, 0)
										 };
		}
		else
		{
			boundaries = new HalfSpace[] {new HalfSpace(-EXTENT_WIDTH, -EXTENT_WIDTH/2, 0, 1),
										  new HalfSpace(EXTENT_WIDTH, EXTENT_WIDTH/2, 0, -1)
					                     };
		}
		objects = new ArrayList<PhysicsObject>();
		
		objects.add(new Circle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
                              10, 10,
                              0, 0, EXTENT_WIDTH * .03f, null, null));
		
//		objects.add(new Triangle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
//			                                10, 10,
//			                                0, 0, EXTENT_WIDTH * .1f, null));
		
		if(numPlayer == 1)
			paddles[0] = new Paddle(-20, 0, 1, 0);
		else {
			paddles[0] = new Paddle(-20, 0, 1, 0);
			paddles[1] = new Paddle(20, 0, -1, 0);
		}
	}

	private void createAndShowGUI() {
		// Fix for background flickering on some platforms
		System.setProperty("sun.awt.noerasebackground", "true");

		GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
		final Canvas3D canvas3D = new Canvas3D(config);
		SimpleUniverse simpleU = new SimpleUniverse(canvas3D);
		simpleU.getViewingPlatform().setNominalViewingTransform();
		simpleU.getViewer().getView().setSceneAntialiasingEnable(true);
		
		// Add a scaling transform that resizes the virtual world to fit
		// within the standard view frustum.
		BranchGroup trueScene = new BranchGroup();
		TransformGroup worldScaleTG = new TransformGroup();
		Transform3D t3D = new Transform3D();
		t3D.setScale(.9 / EXTENT_WIDTH);
		worldScaleTG.setTransform(t3D);
		trueScene.addChild(worldScaleTG);
		scene = new BranchGroup();
		scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
		scene.setCapability(BranchGroup.ALLOW_DETACH);
		scene.setCapability(Group.ALLOW_CHILDREN_WRITE);
		scene.setCapability(Group.ALLOW_CHILDREN_READ);
		scene.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		scene.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		worldScaleTG.addChild(scene);
		
		final TransformGroup extentTransform = new TransformGroup();
		extentTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		if(numPlayer == 1)
			extentTransform.addChild(createExtent());
		else if(numPlayer == 2){
			extentTransform.addChild(topExtent());
			extentTransform.addChild(botExtent());
		}
			
			
		scene.addChild(extentTransform);
		for (PhysicsObject o : objects)
			scene.addChild(o.BG);
		for(PhysicsObject o : paddles)
			scene.addChild(o.BG);
		
		
		simpleU.addBranchGraph(trueScene);
		
		appFrame = new JFrame("Pong Evolution");
		appFrame.setLayout(new BorderLayout());
		appFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
		appFrame.add(canvas3D, BorderLayout.CENTER);
		appFrame.add(createScorePanel(), BorderLayout.EAST);
		createMenu(appFrame, canvas3D);
		appFrame.pack();
		
		if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH))
			appFrame.setExtendedState(appFrame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
		
		paddleControls(canvas3D);
		
		timer = new Timer(1000 / UPDATE_RATE, new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if(!gameOver)
				{
					canvas3D.stopRenderer();
					tick();
					canvas3D.startRenderer();
				}
			}
		});
		timer.start();
		
		appFrame.setVisible(true);
	}
	
	private void tick() 
	{
		p1Score += 1;
		if(numPlayer == 2)
		{
			p2Score += 1;
		}
		
		if((p1Score > 1000 || p2Score > 1000) && objects.size() < 2)
		{
			multishape = new Triangle(1, (float)(Math.random() - .5) * EXTENT_WIDTH, (float)(Math.random() - .5) * EXTENT_WIDTH,
                    10, 10,
                    0, 0, EXTENT_WIDTH * .1f, null);
			objects.add(multishape);
			scene.addChild(objects.get(objects.size() - 1).BG);
		}
		
		for (PhysicsObject o : objects) 
		{
			o.updateState(1f / UPDATE_RATE);
			if(o.position.x < -21)
			{
				o.position.x = 19;
				Random random = new Random();
				o.velocity.x = -(random.nextFloat() * 20);
				o.velocity.y = -(random.nextFloat() * 20);
				p1Score -= 200;
				
				if(numPlayer == 2)
				{
					p2Score += 100;
				}
				
				if(p1Score < 0)
				{
					if(numPlayer == 2)
					{
						timer.stop();
						JOptionPane.showMessageDialog(null, "Player 2 won this round!", "Winner", JOptionPane.INFORMATION_MESSAGE);
						timer.start();
					}
				
					else
					{
						timer.stop();
						JOptionPane.showMessageDialog(null, "You Lose", "Loser", JOptionPane.INFORMATION_MESSAGE);
						timer.start();
					}
				}
			}
			else if(o.position.x > 21)
			{
				o.position.x = -19;
				Random random = new Random();
				o.velocity.x = random.nextFloat() * 20;
				o.velocity.y = random.nextFloat() * 20;
				
				if(numPlayer == 1)
				{
					p1Score += 100;
				}
				
				else
				{
					p2Score -= 200;
				}
				
				if(p2Score < 0)
				{
					timer.stop();
					JOptionPane.showMessageDialog(null, "Player 1 won this round!", "Winner", JOptionPane.INFORMATION_MESSAGE);
					timer.start();
				}
			}
		}
		for (int i = 0; i < COLLISION_ITERATIONS; i++)
			for (PhysicsObject o : objects) {
				for (HalfSpace hs : boundaries)
					CollisionHandler.checkAndResolveCollision(hs, o);
				for (PhysicsObject o2 : objects)
					CollisionHandler.checkAndResolveCollision(o2, o);
				for(PhysicsObject paddle : paddles)
				{
					CollisionHandler.checkAndResolveCollision(paddle, o);
				}
			}
		for (PhysicsObject o : objects) {
			o.updateTransformGroup();
			// Clear the object's force accumulator.
			o.forceAccumulator.x = o.forceAccumulator.y = 0;
		}
		
		p1ScoreLabel.setText("Score: " + p1Score); 
		
		if(numPlayer == 2)
		{
			p2ScoreLabel.setText("Score: " + p2Score); 
		}
		
		if(p1Score < 0 || p2Score < 0)
		{
			String[] choice = {"Play Again?", "Quit"};
			gameOver = true;
			String winner = null;
			if(p1Score < 0 && numPlayer == 2)
			{
				winner = "Player 2 Wins";
			}
			else if(p2Score < 0)
			{
				winner = "Player 1 Wins";
			}
			
			else if(p1Score < 0 && numPlayer == 1)
			{
				winner = "You Lose";
			}
			
			int input = JOptionPane.showOptionDialog(null, 
					"Game Over. " + winner, "Mode", 
					JOptionPane.YES_NO_OPTION, 
					JOptionPane.WARNING_MESSAGE, null, choice, choice[0]);
			
			if (input == JOptionPane.CLOSED_OPTION){
				System.exit(0);
			}
			
			if(input == 0)
			{
				p1Score = 0;
				p2Score = 0;
				gameOver = false;
			}
			else if(input == 1)
			{
				WindowEvent windowClosing = new WindowEvent(appFrame, WindowEvent.WINDOW_CLOSING);
				appFrame.dispatchEvent(windowClosing);
			}
			
		}
	}

	private static Node createExtent() {
		float[] coordinates = {-EXTENT_WIDTH, -EXTENT_WIDTH/2, 0,
		                       EXTENT_WIDTH, -EXTENT_WIDTH/2, 0,
		                       EXTENT_WIDTH, EXTENT_WIDTH/2, 0,
		                       -EXTENT_WIDTH, EXTENT_WIDTH/2, 0};
		LineStripArray geometry = new LineStripArray(4, GeometryArray.COORDINATES, new int[] {4});
		
		geometry.setCoordinates(0, coordinates);
		Shape3D shape = new Shape3D(geometry);
		Appearance appearance = new Appearance();
		appearance.setColoringAttributes(new ColoringAttributes(1f, 1f, 1f, ColoringAttributes.FASTEST));
		shape.setAppearance(appearance);
		
		return shape;
	}
	
	private static Node botExtent() {
		float[] coordinates = {EXTENT_WIDTH, EXTENT_WIDTH/2, 0,
		                       -EXTENT_WIDTH, EXTENT_WIDTH/2, 0};
		LineStripArray geometry = new LineStripArray(2, GeometryArray.COORDINATES, new int[] {2});
		
		geometry.setCoordinates(0, coordinates);
		Shape3D shape = new Shape3D(geometry);
		Appearance appearance = new Appearance();
		appearance.setColoringAttributes(new ColoringAttributes(1f, 1f, 1f, ColoringAttributes.FASTEST));
		shape.setAppearance(appearance);
		
		return shape;
	}
	private static Node topExtent() {
		float[] coordinates = {-EXTENT_WIDTH, -EXTENT_WIDTH/2, 0,
                			   EXTENT_WIDTH, -EXTENT_WIDTH/2, 0};
		LineStripArray geometry = new LineStripArray(2, GeometryArray.COORDINATES, new int[] {2});
		
		geometry.setCoordinates(0, coordinates);
		Shape3D shape = new Shape3D(geometry);
		Appearance appearance = new Appearance();
		appearance.setColoringAttributes(new ColoringAttributes(1f, 1f, 1f, ColoringAttributes.FASTEST));
		shape.setAppearance(appearance);
		
		return shape;
	}
	
	private JPanel createScorePanel()
	{
		GridLayout scorePanelLayout = new GridLayout(4, 1);
		JPanel scorePanel = new JPanel(scorePanelLayout);
		scorePanel.setBackground(new Color(0, 0, 0));
		scorePanel.setPreferredSize(new Dimension(70, 100));
		JLabel player1Title = new JLabel("Player 1");
		player1Title.setForeground(Color.BLUE);
		scorePanel.add(player1Title);
		p1ScoreLabel = new JLabel("Score: 0");
		p1ScoreLabel.setForeground(Color.BLUE);
		scorePanel.add(p1ScoreLabel);
		if(numPlayer == 2)
		{
			JLabel player2Title = new JLabel("Player 2");
			player2Title.setForeground(Color.RED);
			scorePanel.add(player2Title);
			p2ScoreLabel = new JLabel("Score: 0");
			p2ScoreLabel.setForeground(Color.RED);
			scorePanel.add(p2ScoreLabel);
		}
		
		return scorePanel;
	}
	
	private void paddleControls(Canvas3D canvas3D)
	{
		canvas3D.addKeyListener(new KeyListener()
		{
			@Override
			public void keyReleased(KeyEvent arg0) {
				
			}

			@Override
			public void keyTyped(KeyEvent arg0) {
				
			}
			
			@Override
			public void keyPressed(KeyEvent event) 
			{
				int key = event.getKeyCode();
				float p1Movement = 0;
				float p2Movement = 0;
				switch(key)
				{
					case KeyEvent.VK_W:
					{
						if(numPlayer == 2)
							p1Movement = 1f;
						break;
					}
					
					case KeyEvent.VK_S:
					{
						if(numPlayer == 2)
							p1Movement = -1f;
						break;
					}
					
					case KeyEvent.VK_UP:
					{
						if(numPlayer == 1) 
							p1Movement = 1f;
						else
							p2Movement = 1f;
						break;
					}
					case KeyEvent.VK_DOWN:
					{
						if(numPlayer == 1) 
							p1Movement = -1f;
						else
							p2Movement = -1f;
					}
				}
				
				Paddle player1Paddle = (Paddle)paddles[0];
				player1Paddle.move(p1Movement);
				
				if(numPlayer == 2)
				{
					Paddle player2Paddle = (Paddle)paddles[1];
					player2Paddle.move(p2Movement);
				}
			}
		});
	}
	
	public void createMenu(final JFrame frame, final Canvas3D canvas3D) {
		JMenuBar menu = new JMenuBar();
		JMenu gameBar = new JMenu("Game");
		JMenu helpBar = new JMenu("Help");
		JMenuItem about = new JMenuItem("About");
		JMenuItem help = new JMenuItem("Help");
		JMenuItem newGame = new JMenuItem("New Game");
		JMenuItem quit = new JMenuItem("Quit");
		JMenuItem pause = new JMenuItem("Pause");
		
		newGame.addActionListener(new ActionListener() {		
			@Override
			public void actionPerformed(ActionEvent e) 
			{
				Circle temp = (Circle) objects.get(0);
				temp.position.x = 19;
				Random random = new Random();
				temp.velocity.x = -(random.nextFloat() * 20);
				temp.velocity.y = -(random.nextFloat() * 20);
				
				//Remove multishape
				if(objects.size() > 10)
				{
					objects.remove(1);
					scene.removeChild(multishape.BG);
				}
				
				p1Score = 0;
				p2Score = 0;
				gameOver = false;
			}
		});
		pause.setAccelerator(KeyStroke.getKeyStroke("F1"));
		newGame.setAccelerator(KeyStroke.getKeyStroke("F2"));
		pause.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				timer.stop();
				JOptionPane.showMessageDialog(frame, "Game is Paused.", "Paused", JOptionPane.WARNING_MESSAGE);
				timer.start();
			}
		});
		
		quit.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				System.exit(0);
			}
		});
		help.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				timer.stop();
				if(numPlayer ==1)
					JOptionPane.showMessageDialog(frame, "Player 1: Up key - Up, Down key - Down\n                Avoid missing ball.", "Help", JOptionPane.INFORMATION_MESSAGE);
				else if(numPlayer ==2)
					JOptionPane.showMessageDialog(frame, "Player 1: W - Up, S - Down \nPlayer 2: Up key - Up, Down key - Down\nWinner gets 100 points, loser -200 points.", "Help", JOptionPane.INFORMATION_MESSAGE);
				timer.start();
			}
		});
		about.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				timer.stop();
				JOptionPane.showMessageDialog(frame, "Created by: Tyler Simrell & Wilson Burhan", "About Us", JOptionPane.INFORMATION_MESSAGE);
				timer.start();
			}
		});
		helpBar.add(help);
		helpBar.addSeparator();
		helpBar.add(about);
		
		gameBar.add(newGame);
		gameBar.add(pause);
		gameBar.addSeparator();
		gameBar.add(quit);
		menu.add(gameBar);
		menu.add(helpBar);
		frame.setJMenuBar(menu);
	}
}